/*
	This file is part of PropExtractor.

	AliceDecrypt is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	AliceDecrypt is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with PropExtractor. If not, see <http://www.gnu.org/licenses/>.
*/

#include "PEAliceSoftImage.h"

#include "PETools.h"
#include "PEAliceSoftZip.h"

namespace pe
{

	// ================================
	// Image
	// ================================

	int AliceImageReadHeader(AliceImageHeader* a_Header, PEFile* a_File)
	{
		fread(a_Header, sizeof(AliceImageHeader), 1, a_File->handle);

		if (a_Header->header[0] != 'Q' ||
			a_Header->header[1] != 'N' ||
			a_Header->header[2] != 'T')
		{
			return -1;
		}

		return 0;
	}

	int AliceImageDecompress(PEImageRaw* a_Target, AliceImageHeader* a_Header, PEFile* a_File)
	{
		if (!a_Target) { return -1; }
		if (!a_Header) { return -2; }
		if (!a_File) { return -3; }

		a_Target->width = a_Header->width;
		a_Target->height = a_Header->height;
		a_Target->bpp = (byte)(a_Header->bpp >> 3);

		if (a_Target->bpp == 0) { return -1; }

		a_Target->data = new byte[a_Target->width * a_Target->height * a_Target->bpp];

		ALICE_DEBUG("Reading %s...", a_File->path);

		z_stream zlib_stream;
		zlib_stream.zalloc = Z_NULL;
		zlib_stream.zfree = Z_NULL;
		zlib_stream.opaque = Z_NULL;
		zlib_stream.avail_in = 0;
		zlib_stream.next_in = Z_NULL;

		inflateInit(&zlib_stream);

		byte* dst;
		unsigned int have;
		int ret;
		unsigned char in[CHUNK];
		unsigned char out[CHUNK];

		byte* pixels = new byte[a_Target->width * a_Target->height * a_Target->bpp];
		memset(pixels, 0, a_Target->width * a_Target->height * a_Target->bpp);
		dst = pixels;

		unsigned int total_read = 0;

		// using zlib to decompress

		do 
		{
			zlib_stream.avail_in = fread(in, 1, CHUNK, a_File->handle);
			if (ferror(a_File->handle)) 
			{
				(void)inflateEnd(&zlib_stream);
				return Z_ERRNO;
			}
			if (zlib_stream.avail_in == 0) 
			{ 
				break; 
			}
			zlib_stream.next_in = in;

			do 
			{
				zlib_stream.avail_out = CHUNK;
				zlib_stream.next_out = out;
				ret = inflate(&zlib_stream, Z_NO_FLUSH);
				switch (ret) 
				{
				case Z_NEED_DICT:
					ret = Z_DATA_ERROR;					

				case Z_DATA_ERROR:
				case Z_MEM_ERROR:
					(void)inflateEnd(&zlib_stream);
					return ret;
				}

				have = CHUNK - zlib_stream.avail_out;
				total_read += have;
				memcpy(dst, out, have);
				dst += have;
			}
			while (zlib_stream.avail_out == 0);
		} 
		while (ret != Z_STREAM_END);

		ALICE_DEBUG("Image dimensions: %i x %i", a_Header->width, a_Header->height);

		ALICE_DEBUG("Image size: %i", total_read);

		// reverse obfuscation

		byte* src_r = pixels;
		byte* src_g = pixels + (a_Target->width * a_Target->height * 1);
		byte* src_b = pixels + (a_Target->width * a_Target->height * 2);

		dst = a_Target->data;

		for (unsigned int y = 0; y < a_Target->height; y += 2)
		{
			byte* read_r = src_r + (y * a_Target->width);
			byte* read_g = src_g + (y * a_Target->width);
			byte* read_b = src_b + (y * a_Target->width);

			byte* dst_ul = dst;
			byte* dst_ll = dst + (a_Target->width * 3);
			byte* dst_ur = dst + 3;
			byte* dst_lr = dst + ((a_Target->width + 1) * 3);

			for (unsigned int x = 0; x < a_Target->width; x += 2)
			{						
				dst_ul[0] = read_r[0];
				dst_ll[0] = read_r[1];
				dst_ur[0] = read_r[2];
				dst_lr[0] = read_r[3];

				dst_ul[1] = read_g[0];
				dst_ll[1] = read_g[1];
				dst_ur[1] = read_g[2];
				dst_lr[1] = read_g[3];

				dst_ul[2] = read_b[0];
				dst_ll[2] = read_b[1];
				dst_ur[2] = read_b[2];
				dst_lr[2] = read_b[3];

				dst_ul += 6;
				dst_ll += 6;
				dst_ur += 6;
				dst_lr += 6;

				read_r += 4;
				read_g += 4;
				read_b += 4;
			}

			dst += a_Target->width * 6;
		}

		for (unsigned int x = 1; x < a_Target->width; x++)
		{
			a_Target->data[x * 3 + 0] = a_Target->data[(x - 1) * 3 + 0] - a_Target->data[x * 3 + 0];
			a_Target->data[x * 3 + 1] = a_Target->data[(x - 1) * 3 + 1] - a_Target->data[x * 3 + 1];
			a_Target->data[x * 3 + 2] = a_Target->data[(x - 1) * 3 + 2] - a_Target->data[x * 3 + 2];
		}

		for (unsigned int y = 1; y < a_Target->height; y++)
		{
			a_Target->data[(y * a_Target->width) * 3 + 0] = a_Target->data[((y - 1) * a_Target->width) * 3 + 0] - a_Target->data[(y * a_Target->width) * 3 + 0];
			a_Target->data[(y * a_Target->width) * 3 + 1] = a_Target->data[((y - 1) * a_Target->width) * 3 + 1] - a_Target->data[(y * a_Target->width) * 3 + 1];
			a_Target->data[(y * a_Target->width) * 3 + 2] = a_Target->data[((y - 1) * a_Target->width) * 3 + 2] - a_Target->data[(y * a_Target->width) * 3 + 2];

			for (unsigned int x = 1; x < a_Target->width; x++)
			{
				byte px, py;

				py = a_Target->data[((y - 1) * a_Target->width + (x    )) * 3 + 0];
				px = a_Target->data[((y    ) * a_Target->width + (x - 1)) * 3 + 0];
				a_Target->data[(y * a_Target->width + x) * 3 + 0] = ((py + px) >> 1) - a_Target->data[(y * a_Target->width + x) * 3 + 0];

				py = a_Target->data[((y - 1) * a_Target->width + (x    )) * 3 + 1];
				px = a_Target->data[((y    ) * a_Target->width + (x - 1)) * 3 + 1];
				a_Target->data[(y * a_Target->width + x) * 3 + 1] = ((py + px) >> 1) - a_Target->data[(y * a_Target->width + x) * 3 + 1];

				py = a_Target->data[((y - 1) * a_Target->width + (x    )) * 3 + 2];
				px = a_Target->data[((y    ) * a_Target->width + (x - 1)) * 3 + 2];
				a_Target->data[(y * a_Target->width + x) * 3 + 2] = ((py + px) >> 1) - a_Target->data[(y * a_Target->width + x) * 3 + 2];
			}
		}

		return 0;
	}

	int AliceZipSaveImageToFile(AliceZip* a_ZipFile, unsigned int a_Index, const char* a_Pattern)
	{
		PEFile read;
		AliceZipOpenFile(&read, a_ZipFile, a_Index);

		AliceImageHeader header;
		if (AliceImageReadHeader(&header, &read) != 0) { return -1; }

		PEImageRaw raw;
		AliceImageDecompress(&raw, &header, &read);

		char path[_MAX_PATH];
		size_t path_len;
		if (a_Pattern)
		{
			GetPatternedPath(path, &path_len, a_Pattern, read.path, a_ZipFile->workingdir, a_Index);
		}
		else
		{
			strcpy(path, a_ZipFile->workingdir);
			strcat(path, read.path);
		}

		PEImageRawSaveToTGA(&raw, path);

		return 0;
	}

}; // namespace pe